package ejb;

import static ejb.Orden.ASC;
import static ejb.Orden.DESC;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.Root;

public abstract class FacadeAbstracto<T> {

    private Class<T> entityClass;
    private Orden orden;
    private String campoOrdenado;

    /**
     * Creo el facade en base a la clase que le paso como parametro
     * @param entityClass clase de la entidad 
     */
    public FacadeAbstracto(Class<T> entityClass) {
        this.entityClass = entityClass;
        this.orden = Orden.NINGUNO;
        this.campoOrdenado = null;
    }

    protected abstract EntityManager getEntityManager();

    /**
     * Crea un objeto nuevo 
     * @param entity Es la clase del objeto que quiero crear.
     * @throws Exception 
     */
    public void crear(T entity) throws Exception {
        try {
            getEntityManager().persist(entity);
        } catch (Exception e) {
            throw e;
        }

    }

    /**
     * Edito un objeto. Le paso la clase del mismo. 
     * @param entity objeto que quiero crear
     * @throws Exception  
     */
    public void editar(T entity) throws Exception {
        try {
            getEntityManager().merge(entity);

        } catch (Exception e) {
            throw e;
        }
    }
/**
 * Elimino el objeto. Paso como parametro el mismo.
 * @param entity
 * @throws Exception 
 */
    public void eliminar(T entity) throws Exception {
        try {
            Class clase = entity.getClass();
            Method metodo = clase.getMethod("setActivo", boolean.class);
            metodo.invoke(entity, false);
            getEntityManager().merge(entity);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw e;
        }
    }

    /**
     * Busco el objeto por id
     * @param id Objeto que paso como parametro para realizar la consulta
     * @return
     * @throws Exception 
     */
    public T buscarPorId(Object id) throws Exception {
        return getEntityManager().find(entityClass, id);
    }

    /**
     * Funcion que devuelve lista de objetos  de mi facade
     * 
     * @return
     * @throws Exception 
     */
    public List<T> listarTodo() throws Exception {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        Root<T> c = cq.from(entityClass);
        cq.select(c);
        if (this.campoOrdenado != null && this.orden != Orden.NINGUNO) {
            if (!"".equals(this.campoOrdenado.trim())) {
                switch (orden) {
                    case ASC:
                        cq.orderBy(getEntityManager().getCriteriaBuilder().asc(c.get(this.campoOrdenado)));
                        break;
                    case DESC:
                        cq.orderBy(getEntityManager().getCriteriaBuilder().desc(c.get(this.campoOrdenado)));
                        break;
                }
            }
        }
        return getEntityManager().createQuery(cq).getResultList();
    }

    /**
     * Devuelve una lista de objetos dado  el rango que le paso como parametro
     * @param rango Array de int, con cantidad inicial y cantidad final.
     * @return
     * @throws Exception 
     */
    public List<T> buscarRango(int[] rango) throws Exception {
//        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
//        Root<T> c = cq.from(entityClass);
//        cq.select(c);
//        javax.persistence.Query q = getEntityManager().createQuery(cq);
//        if (this.campoOrdenado != null && this.orden != Orden.NINGUNO) {
//            if (!"".equals(this.campoOrdenado.trim())) {
//                switch (orden) {
//                    case ASC:
//                        cq.orderBy(getEntityManager().getCriteriaBuilder().asc(c.get(this.campoOrdenado)));
//                        break;
//                    case DESC:
//                        cq.orderBy(getEntityManager().getCriteriaBuilder().desc(c.get(this.campoOrdenado)));
//                        break;
//                }
//            }
//        }
//        q.setMaxResults(rango[1] - rango[0]);
//        q.setFirstResult(rango[0]);
//        return q.getResultList();
        return this.buscarConParametros(null, null, rango);

    }

    /**
     * Cuento la canitdad de objetos
     * @return
     * @throws Exception 
     */
    public int contar() throws Exception {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }

    /** 
     *  Me devuelve una lista de objetsis que cumpla con la lista  de condicionales
     * que le envio.
     * @param condicionales
     * @return
     * @throws Exception 
     */
    public List<T> buscarConParametros(List<Condicional> condicionales) throws Exception {
        int[] rango = new int[]{-1, -1};
        return buscarConParametros(condicionales, null, rango);
    }

    /**
     * 
     * @param condicionales
     * @param adicional
     * @return
     * @throws Exception 
     */
    public List<T> buscarConParametros(List<Condicional> condicionales, String adicional) throws Exception {
        int[] rango = new int[]{-1, -1};
        return buscarConParametros(condicionales, adicional, rango);
    }

    /**
     * 
     * @param condicionales
     * @param adicional
     * @param rango
     * @return
     * @throws Exception 
     */
    public List<T> buscarConParametros(List<Condicional> condicionales, String adicional, int[] rango) throws Exception {
//        String consulta = "select x from " + entityClass.getName().substring(9) + " x ";
        String cont = "";
        if (adicional != null && adicional.trim().toLowerCase().equals("count")) {
            cont = "count";
        }
        String consulta = "select " + cont + " x from " + entityClass.getSimpleName() + " x ";
        boolean where = false;
        if (condicionales != null) {
            for (Iterator<Condicional> it = condicionales.iterator(); it.hasNext();) {
                Condicional condicional = it.next();
                if (condicional != null) {
                    consulta += (where) ? " and " + condicional.obtenerCondicional() : "where " + condicional.obtenerCondicional();
                    where = true;
                }
            }
        }
        if (this.campoOrdenado != null && this.orden != Orden.NINGUNO) {
            if (!"".equals(this.campoOrdenado.trim())) {
                consulta += " order by x." + this.campoOrdenado + " " + orden.toString();
            }
        }
        consulta += (adicional != null) ? " " + adicional + ";" : ";";
        return ejecutarConsulta(consulta, rango);
    }

    /**
     * 
     * @param condicionales
     * @param adicional
     * @return
     * @throws Exception 
     */
    public int contarConParametros(List<Condicional> condicionales, String adicional) throws Exception {
        String consulta = "select count (x) from " + entityClass.getSimpleName() + " x ";
        boolean where = false;
        if (condicionales != null) {
            for (Iterator<Condicional> it = condicionales.iterator(); it.hasNext();) {
                Condicional condicional = it.next();
                if (condicional != null) {
                    consulta += (where) ? " and " + condicional.obtenerCondicional() : "where " + condicional.obtenerCondicional();
                    where = true;
                }
            }
        }
        Query q = getEntityManager().createQuery(consulta);
        return ((Long) q.getSingleResult()).intValue();
    }

    /**
     * Devuelve como resultado una lista de objetos en base a la consulta y rango
     * @param consulta string que equivale a consulta jpql
     * @param rango cantidad inicial y cantidad final
     * @return
     * @throws Exception 
     */
    private List<T> ejecutarConsulta(String consulta, int[] rango) throws Exception {
        javax.persistence.Query q = getEntityManager().createQuery(consulta);
        if (rango[0] > 0 && rango[1] > 0) {
            q.setMaxResults(rango[1] - rango[0]);
            q.setFirstResult(rango[0]);
        }
        return q.getResultList();
    }

    /**
     * Establezco por el campo que voy a ordenar las consultas.
     * @param campoOrdenado
     * @param orden
     * @throws Exception 
     */
    public void establecerOrdenacion(String campoOrdenado, Orden orden) throws Exception {
        this.campoOrdenado = campoOrdenado;
        this.orden = orden;
    }
}